/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.log4j.jmx;

import java.lang.reflect.Constructor;
import org.apache.log4j.*;

import org.apache.log4j.spi.HierarchyEventListener;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.helpers.OptionConverter;

import java.util.Vector;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;

import javax.management.ObjectName;
import javax.management.MBeanInfo;
import javax.management.Attribute;

import javax.management.MBeanException;
import javax.management.AttributeNotFoundException;
import javax.management.RuntimeOperationsException;
import javax.management.ReflectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationBroadcaster;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.NotificationFilter;
import javax.management.NotificationFilterSupport;
import javax.management.ListenerNotFoundException;

public class HierarchyDynamicMBean extends AbstractDynamicMBean
		implements
			HierarchyEventListener,
			NotificationBroadcaster {

	static final String ADD_APPENDER = "addAppender.";
	static final String THRESHOLD = "threshold";

	private MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
	private MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1];

	private Vector vAttributes = new Vector();
	private String dClassName = this.getClass().getName();
	private String dDescription = "This MBean acts as a management facade for org.apache.log4j.Hierarchy.";

	private NotificationBroadcasterSupport nbs = new NotificationBroadcasterSupport();

	private LoggerRepository hierarchy;

	private static Logger log = Logger.getLogger(HierarchyDynamicMBean.class);

	public HierarchyDynamicMBean() {
		hierarchy = LogManager.getLoggerRepository();
		buildDynamicMBeanInfo();
	}

	private void buildDynamicMBeanInfo() {
		Constructor[] constructors = this.getClass().getConstructors();
		dConstructors[0] = new MBeanConstructorInfo(
				"HierarchyDynamicMBean(): Constructs a HierarchyDynamicMBean instance",
				constructors[0]);

		vAttributes.add(new MBeanAttributeInfo(THRESHOLD, "java.lang.String",
				"The \"threshold\" state of the hiearchy.", true, true, false));

		MBeanParameterInfo[] params = new MBeanParameterInfo[1];
		params[0] = new MBeanParameterInfo("name", "java.lang.String",
				"Create a logger MBean");
		dOperations[0] = new MBeanOperationInfo("addLoggerMBean",
				"addLoggerMBean(): add a loggerMBean", params,
				"javax.management.ObjectName", MBeanOperationInfo.ACTION);
	}

	public ObjectName addLoggerMBean(String name) {
		Logger cat = LogManager.exists(name);

		if (cat != null) {
			return addLoggerMBean(cat);
		} else {
			return null;
		}
	}

	ObjectName addLoggerMBean(Logger logger) {
		String name = logger.getName();
		ObjectName objectName = null;
		try {
			LoggerDynamicMBean loggerMBean = new LoggerDynamicMBean(logger);
			objectName = new ObjectName("log4j", "logger", name);

			if (!server.isRegistered(objectName)) {
				server.registerMBean(loggerMBean, objectName);
				NotificationFilterSupport nfs = new NotificationFilterSupport();
				nfs.enableType(ADD_APPENDER + logger.getName());
				log.debug("---Adding logger [" + name + "] as listener.");
				nbs.addNotificationListener(loggerMBean, nfs, null);
				vAttributes.add(new MBeanAttributeInfo("logger=" + name,
						"javax.management.ObjectName", "The " + name
								+ " logger.", true, true, // this makes the
															// object
						// clickable
						false));

			}

		} catch (Exception e) {
			log.error("Could not add loggerMBean for [" + name + "].", e);
		}
		return objectName;
	}

	public void addNotificationListener(NotificationListener listener,
			NotificationFilter filter, java.lang.Object handback) {
		nbs.addNotificationListener(listener, filter, handback);
	}

	protected Logger getLogger() {
		return log;
	}

	public MBeanInfo getMBeanInfo() {
		// cat.debug("getMBeanInfo called.");

		MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[vAttributes
				.size()];
		vAttributes.toArray(attribs);

		return new MBeanInfo(dClassName, dDescription, attribs, dConstructors,
				dOperations, new MBeanNotificationInfo[0]);
	}

	public MBeanNotificationInfo[] getNotificationInfo() {
		return nbs.getNotificationInfo();
	}

	public Object invoke(String operationName, Object params[],
			String signature[]) throws MBeanException, ReflectionException {

		if (operationName == null) {
			throw new RuntimeOperationsException(new IllegalArgumentException(
					"Operation name cannot be null"),
					"Cannot invoke a null operation in " + dClassName);
		}
		// Check for a recognized operation name and call the corresponding
		// operation

		if (operationName.equals("addLoggerMBean")) {
			return addLoggerMBean((String) params[0]);
		} else {
			throw new ReflectionException(new NoSuchMethodException(
					operationName), "Cannot find the operation "
					+ operationName + " in " + dClassName);
		}

	}

	public Object getAttribute(String attributeName)
			throws AttributeNotFoundException, MBeanException,
			ReflectionException {

		// Check attributeName is not null to avoid NullPointerException later
		// on
		if (attributeName == null) {
			throw new RuntimeOperationsException(new IllegalArgumentException(
					"Attribute name cannot be null"),
					"Cannot invoke a getter of " + dClassName
							+ " with null attribute name");
		}

		log.debug("Called getAttribute with [" + attributeName + "].");

		// Check for a recognized attributeName and call the corresponding
		// getter
		if (attributeName.equals(THRESHOLD)) {
			return hierarchy.getThreshold();
		} else if (attributeName.startsWith("logger")) {
			int k = attributeName.indexOf("%3D");
			String val = attributeName;
			if (k > 0) {
				val = attributeName.substring(0, k) + '='
						+ attributeName.substring(k + 3);
			}
			try {
				return new ObjectName("log4j:" + val);
			} catch (Exception e) {
				log.error("Could not create ObjectName" + val);
			}
		}

		// If attributeName has not been recognized throw an
		// AttributeNotFoundException
		throw (new AttributeNotFoundException("Cannot find " + attributeName
				+ " attribute in " + dClassName));

	}

	public void addAppenderEvent(Category logger, Appender appender) {
		log.debug("addAppenderEvent called: logger=" + logger.getName()
				+ ", appender=" + appender.getName());
		Notification n = new Notification(ADD_APPENDER + logger.getName(),
				this, 0);
		n.setUserData(appender);
		log.debug("sending notification.");
		nbs.sendNotification(n);
	}

	public void removeAppenderEvent(Category cat, Appender appender) {
		log.debug("removeAppenderCalled: logger=" + cat.getName()
				+ ", appender=" + appender.getName());
	}

	public void postRegister(java.lang.Boolean registrationDone) {
		log.debug("postRegister is called.");
		hierarchy.addHierarchyEventListener(this);
		Logger root = hierarchy.getRootLogger();
		addLoggerMBean(root);
	}

	public void removeNotificationListener(NotificationListener listener)
			throws ListenerNotFoundException {
		nbs.removeNotificationListener(listener);
	}

	public void setAttribute(Attribute attribute)
			throws AttributeNotFoundException, InvalidAttributeValueException,
			MBeanException, ReflectionException {

		// Check attribute is not null to avoid NullPointerException later on
		if (attribute == null) {
			throw new RuntimeOperationsException(new IllegalArgumentException(
					"Attribute cannot be null"), "Cannot invoke a setter of "
					+ dClassName + " with null attribute");
		}
		String name = attribute.getName();
		Object value = attribute.getValue();

		if (name == null) {
			throw new RuntimeOperationsException(new IllegalArgumentException(
					"Attribute name cannot be null"),
					"Cannot invoke the setter of " + dClassName
							+ " with null attribute name");
		}

		if (name.equals(THRESHOLD)) {
			Level l = OptionConverter.toLevel((String) value, hierarchy
					.getThreshold());
			hierarchy.setThreshold(l);
		}

	}
}
